Header menu logo fantomas

Fantomas is trying to format the input multiple times due to the detection of multiple defines

As explained in Formatting Conditional Compilation Directives, Fantomas will try to format the input multiple times if it detects multiple defines.
The amount of conditional directives should be exactly the same in each pass. This is a requirement in order for Fantomas to merge the results into one.
Unfortunately, this is not always the case and an exception will be thrown if the bookkeeping doesn't add up.

As a case-study, we will look at issue #2844 and see how we troubleshoot these types of issues.

System.FormatException: Fantomas is trying to format the input multiple times due to the detection of multiple defines.
There is a problem with merging all the code back together.
[] has 7 fragments
[IOS] has 9 fragments

Isolate each define combination

The first step is to isolate each define combination in a unit test. Doing this will simplify the debugging process.
The formatSourceStringWithDefines can be used to only format the input with a specific set of defines.

[<Test>]
let ``good unit test name, no defines`` () =
    formatSourceStringWithDefines
        []
        """
                        program.SyncAction
                            (
#if IOS
                            // iOS animates by default layout changes, we don't want that
                            fun () -> v
#else
                            fn
#endif
                        )
"""
        config
    |> prepend newline
    |> should
        equal
        """
program.SyncAction(
#if IOS
#else
    fn
#endif
)
"""

Notice that our result code should reflect only the active code branches. IOS is not present and so no code is expected between #if IOS and #else.

If we do this for each combination, we can narrow the problem down to find the troublesome combination.

[<Test>]
let ``good unit test name, IOS`` () =
    formatSourceStringWithDefines
        [ "IOS" ]
        """
                        program.SyncAction
                            (
#if IOS
                            // iOS animates by default layout changes, we don't want that
                            fun () -> v
#else
                            fn
#endif
                        )
"""
        config
    |> prepend newline
    |> fun r ->
        printfn "%s" r
        r
    |> should
        equal
        """
program.SyncAction
    (
#if IOS
    // iOS animates by default layout changes, we don't want that
    fun () -> v
#else
#endif
    )
"""

Result in test explorer

Bringing it all together

If each combination is fixed, we can now try to format the input with all the defines.
Notice that we use formatSourceString instead of formatSourceStringWithDefines here.

[<Test>]
let ``good unit test name, 2844`` () =
    formatSourceString
        false
        """
                        program.SyncAction
                            (
#if IOS
                            // iOS animates by default layout changes, we don't want that
                            fun () -> v
#else
                            fn
#endif
                        )
"""
        config
    |> prepend newline
    |> should
        equal
        """
program.SyncAction
    (
#if IOS
    // iOS animates by default layout changes, we don't want that
    fun () -> v
#else
    fn
#endif
    )
"""

Unit test naming conventions

When dealing with multiple defines, it is important to name the unit tests in a way that makes it easy to understand what is going on.
Use the following naming convention suffix:

namespace System
Multiple items
type FormatException = inherit SystemException new: unit -> unit + 2 overloads
<summary>The exception that is thrown when the format of an argument is invalid, or when a composite format string is not well formed.</summary>

--------------------
System.FormatException() : System.FormatException
System.FormatException(message: string) : System.FormatException
System.FormatException(message: string, innerException: exn) : System.FormatException
val printfn: format: Printf.TextWriterFormat<'T> -> 'T

Type something to start searching.